package com.lzq.train.business.service;

import cn.hutool.core.date.DateTime;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.lzq.train.business.domain.ConfirmOrder;
import com.lzq.train.business.dto.ConfirmOrderMQDto;
import com.lzq.train.business.enums.ConfirmOrderStatusEnum;
import com.lzq.train.business.enums.RocketMQTopicEnum;
import com.lzq.train.business.mapper.ConfirmOrderMapper;
import com.lzq.train.business.req.ConfirmOrderDoReq;
import com.lzq.train.business.req.ConfirmOrderTicketReq;
import com.lzq.train.common.context.LoginMemberContext;
import com.lzq.train.common.exception.BusinessException;
import com.lzq.train.common.exception.BusinessExceptionEnum;
import com.lzq.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.Redisson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;

/**
 * 购票准备 锁阶段
 */
@Service
public class BeforeConfirmOrderService {

    private static final Logger LOG = LoggerFactory.getLogger(BeforeConfirmOrderService.class);

    @Resource
    private Redisson redisson;

    @Resource
    private SkTokenService skTokenService;

    @Resource
    private RocketMQTemplate rocketMQTemplate;

    @Resource
    private ConfirmOrderMapper confirmOrderMapper;

    @SentinelResource(value = "doConfirm", blockHandler = "doConfirmBlock")
    public Long beforeDoConfirm(ConfirmOrderDoReq req) {
        Long id = null;
        for (int i = 1; i <= req.getLineNumber() + 1; i++) { // +1 表示我
            req.setMemberId(LoginMemberContext.getMember().getId());

            // 效验令牌数量
            boolean validSkToken = skTokenService.validSkToken(req.getDate(), req.getTrainCode(), LoginMemberContext.getMember().getId());
            if (!validSkToken) {
                LOG.info("令牌校验失败，令牌数量不足");
                throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_SK_TOKEN_FAIL);
            } else {
                LOG.info("令牌校验成功");
            }

            Date date = req.getDate();
            String trainCode = req.getTrainCode();
            String start = req.getStart();
            String end = req.getEnd();
            List<ConfirmOrderTicketReq> tickets = req.getTickets();
            // 保存确认订单表，状态初始
            DateTime now = DateTime.now();
            ConfirmOrder confirmOrder = new ConfirmOrder();
            confirmOrder.setId(SnowUtil.getSnowflakeNextId());
            confirmOrder.setCreateTime(now);
            confirmOrder.setUpdateTime(now);
            confirmOrder.setMemberId(req.getMemberId());
            confirmOrder.setDate(date);
            confirmOrder.setTrainCode(trainCode);
            confirmOrder.setStart(start);
            confirmOrder.setEnd(end);
            confirmOrder.setDailyTrainTicketId(req.getDailyTrainTicketId());
            confirmOrder.setStatus(ConfirmOrderStatusEnum.INIT.getCode()); // 初始状态
            confirmOrder.setTickets(JSON.toJSONString(tickets));
            confirmOrderMapper.insert(confirmOrder);

            // todo 发送一个MQ 开始购票
            ConfirmOrderMQDto reqMQ = new ConfirmOrderMQDto();
            reqMQ.setLogId(MDC.get("LOG_ID"));
            reqMQ.setDate(req.getDate());
            reqMQ.setTrainCode(req.getTrainCode());

            LOG.info("准备发送MQ，等待出票");
            String reqJSON = JSON.toJSONString(reqMQ);
            LOG.info("排队购票，发送mq开始，消息：{}", reqJSON);
            rocketMQTemplate.convertAndSend(RocketMQTopicEnum.CONFIRM_ORDER.getCode(), reqJSON);
            LOG.info("排队购票，发送mq结束");

            id = confirmOrder.getId();
        }
        return id; // 前端确认订单后返回最后一个订单id（模拟排队）
    }

    /**
     * 降级处理方法，需包含限流方法的所有参数和BlockException参数
     *
     * @param req
     * @param e
     */
    public void doConfirmBlock(ConfirmOrderDoReq req, BlockException e) {
        LOG.info("购票被限流：{}", req);
        // 抛出限流异常
        throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_FLOW_EXCEPTION);
    }
}
